/*
 * @Description:
 * @Author: Junhuang
 * @Date: 2022-04-19 10:00:31
 * @LastEditTime: 2022-05-17 14:43:18
 * @LastEditors: Junhuang
 * @Reference:
 */
'use strict';

const Controller = require('egg').Controller;

// 参数校验规则
const rules = {
  name: { type: 'string', required: true, desc: '部门名称' },
  description: { type: 'string', required: false, desc: '描述' },
  pid: { type: 'int', required: true, desc: '父部门id' },
  status: { type: 'int', required: true, desc: '状态' },
  sort: { type: 'int', required: true, desc: '排序' },
};

class DepartmentController extends Controller {
  async getTree() {
    const { ctx, app } = this;
    let res = await app.model.Department.findAll({
      where: {
        status: 1,
      },
      order: [
        // 将转义标题,并根据有效的方向参数列表验证DESC
        [ 'sort', 'DESC' ],
      ],
      attributes: [ 'id', 'pid', 'name', 'description', 'status', 'sort', 'id_path', 'name_path' ],
    });
    res = JSON.parse(JSON.stringify(res));
    const roots = res.filter(item => {
      return item.pid === 0;
    });
    if (roots && roots.length > 0) {
      roots.forEach(el => {
        el.pname = '无';
        const children = this.getChildren(el.id, res);
        if (children && children.length > 0) {
          el.children = children;
        }
      });
    }
    return ctx.apiSuccesss(roots);
  }

  getChildren(pid, arr_depts) {
    const pd = arr_depts.find(item => {
      return item.id === pid;
    });
    const children = arr_depts.filter(item => {
      return item.pid === pid;
    });
    if (children.length > 0) {
      // 需要递归寻找下一层
      children.forEach(el => {
        el.pname = pd.name;
        const sub_childern = this.getChildren(el.id, arr_depts);
        if (sub_childern && sub_childern.length > 0) {
          el.children = sub_childern;
        }
      });
    } else {
      return [];
    }
    return children;
  }

  async update() {
    const { ctx, app, service } = this;
    const {
      name,
      description,
      status,
      pid,
      sort,
    } = ctx.request.body;

    ctx.validate({
      id: {
        required: true,
        type: 'int',
        desc: '部门id',
      },
      ...rules,
    });

    if (!(status >= -1 && status <= 1)) {
      return ctx.apiFail('不正确的状态');
    }

    const dept = await app.model.Department.findOne({
      where: {
        id: ctx.params.id,
      },
    });

    if (!dept) {
      return ctx.throw(404, '该记录不存在');
    }

    let transaction;
    const ids = []; // 存储name_path发生变化的department_id
    try {
      const temp_name = dept.name;
      const name_path = dept.name_path.replace(',' + temp_name + ',', ',' + name + ',');

      transaction = await this.ctx.model.transaction();
      dept.name = name;
      dept.description = description;
      dept.status = status;
      dept.pid = pid;
      dept.sort = sort;
      dept.name_path = name_path;
      dept.save({ transaction });
      // 如果名字发生变更，则需要同步更新所有子节点的name_path
      if (temp_name !== name) {
        await service.department.refreshPath(dept, ids, transaction);
      }
      await transaction.commit();
    } catch (err) {
      await transaction.rollback();
      return ctx.apiFail('部门修改失败' + err);
    }

    const Op = app.Sequelize.Op;
    const changed_children = await app.model.Department.findAll({
      include: [{
        model: app.model.Department,
        as: 'parant',
        attributes: [ 'id', 'name' ],
      }],
      where: {
        id: {
          [Op.in]: ids,
        },
      },
    });
    const res = JSON.parse(JSON.stringify(dept));
    if (changed_children.length > 0) {
      res.changed_children = [];
      changed_children.forEach(item => {
        const _item = JSON.parse(JSON.stringify(item));
        _item.pname = _item.parant.name;
        delete item.parant;
        res.changed_children.push(_item);
      });
    }
    return ctx.apiSuccesss(res);
  }

  // 新增部门
  async save() {
    const { ctx, app } = this;
    const {
      name,
      description,
      status,
      pid,
    } = ctx.request.body;

    ctx.validate({
      ...rules,
    });

    const pdept = await app.model.Department.findAll({
      where: {
        pid,
      },
    });

    let sort = 1000;
    if (pdept && pdept.length > 0) {
      pdept.forEach(el => {
        if (el.sort < sort) {
          sort = el.sort;
        }
      });
      sort = sort - 1;
      // console.log('sort=', sort);
    }

    const dept = await app.model.Department.create({
      name,
      description,
      status,
      pid,
      sort,
    });

    const mypt = await app.model.Department.findByPk(dept.pid);

    const res = await dept.update({
      id_path: mypt.id_path + dept.id + ',',
      name_path: mypt.name_path + dept.name + ',',
    });

    return ctx.apiSuccesss(res);
  }

  // 更新该部门以下的所有id_path, name_path
  async refreshPath() {
    const { ctx, service } = this;
    const id = ctx.params.id;
    ctx.validate({
      id: {
        required: true,
        type: 'int',
        desc: '部门id',
      },
    });
    const ids = [];
    let transaction;
    try {
      transaction = await this.ctx.model.transaction();
      const dept = await this.ctx.model.Department.findByPk(id, { transaction });
      await service.department.refreshPath(dept, ids, transaction);
      await transaction.commit();
    } catch (error) {
      await transaction.rollback();
      return ctx.apiFail('循环更新失败' + error);
    }
    return ctx.apiSuccesss(ids);
  }

  // 删除，如果该部门有子部门，则不允许删除
  async destroy() {
    const { ctx, app } = this;
    const { id } = ctx.params;
    ctx.validate({
      id: {
        type: 'int', required: true, desc: '部门id',
      },
    });
    const dept = await app.model.Department.findOne({
      where: {
        id,
      },
    });

    if (!dept) {
      return ctx.throw('部门不存在！');
    }

    const res = await dept.destroy();
    ctx.apiSuccesss(`成功删除{id:${res.id},name:${res.name}}的部门`);
  }

  async departmentMove() {
    const { ctx, app } = this;
    const {
      tid,
      position,
    } = ctx.request.body;

    ctx.validate({
      id: {
        required: true,
        type: 'int',
        desc: '部门id',
      },
      tid: {
        required: true,
        type: 'int',
        desc: '部门id',
      },
      position: {
        required: true,
        type: 'string',
        desc: '位置',
      },
    });

    const dept = await app.model.Department.findOne({
      include: [{
        model: app.model.Department,
        as: 'parant',
      }, {
        model: app.model.Department,
        as: 'children',
      }, {
        model: app.model.Admin,
        as: 'members',
        attributes: [ 'id', 'no', 'department_id', 'username', 'name' ],
      }],
      where: {
        id: ctx.params.id,
      },
    });

    if (!dept) {
      return ctx.throw(404, '选择部门不存在');
    }

    const tdept = await app.model.Department.findOne({
      include: [{
        model: app.model.Department,
        as: 'parant',
      }, {
        model: app.model.Department,
        as: 'children',
      }, {
        model: app.model.Admin,
        as: 'members',
        attributes: [ 'id', 'no', 'department_id', 'username', 'name' ],
      }],
      where: {
        id: tid,
      },
    });

    if (!tdept) {
      return ctx.throw(404, '目标部门不存在');
    }

    let isChangedParent = false;
    const o_id_path = dept.id_path;
    const o_name_path = dept.name_path;

    if (position === 'inner') {
      if (dept.pid !== tdept.id) {
        isChangedParent = true;
      }
      if (isChangedParent) {
        // 只有改变了父亲节点，才需要更改id_path, name_path
        dept.pid = tdept.id;
        dept.id_path = tdept.id_path + dept.id + ',';
        dept.name_path = tdept.name_path + dept.name + ',';
      }

      const t_children = tdept.childern;
      let sort = 1000;
      if (t_children && t_children.length > 0) {
        let min = 1000;
        t_children.forEach(el => {
          if (el.sort < min) {
            min = el.sort;
          }
        });
        sort = min - 1;
      }
      dept.sort = sort;
    } else {
      if (dept.pid !== tdept.pid) {
        isChangedParent = true;
      }
      if (isChangedParent) {
        // 只有改变了父亲节点，才需要更改id_path, name_path
        dept.pid = tdept.pid;
        // const pd = await app.model.Department.findByPk(tdept.pid);
        const pd = tdept.parant;
        dept.id_path = pd.id_path + dept.id + ',';
        dept.name_path = pd.name_path + dept.name + ',';
      }
      // dept.sort = position === 'before' ? tdept.sort + 1 : tdept.sort - 1;
      dept.sort = tdept.sort;
    }

    const Op = app.Sequelize.Op;
    const deptAllChildren = await app.model.Department.findAll({
      where: {
        id_path: {
          [Op.like]: '%,' + dept.id + ',%',
        },
      },
    });

    let transaction;
    try {
      // 建立事务对象
      transaction = await this.ctx.model.transaction();
      // 如果是移到某节点的前面或后面，需要将对象节点的兄弟节点的sort都进行更改
      const sequelize = app.model;
      let rawSql = '';
      if (position === 'before') {
        rawSql = 'update sys_department set sort = sort -1 where pid = :pid and sort <= :sort';
      } else if (position === 'after') {
        rawSql = 'update sys_department set sort = sort +1 where pid = :pid and sort >= :sort';
      }
      await sequelize.query(rawSql, {
        replacements: {
          pid: tdept.pid,
          sort: tdept.sort,
        },
        transaction,
      });

      const res = await dept.save({ transaction });

      await Promise.all(
        deptAllChildren.map(async item => {
          if (item.id !== dept.id) {
            const temp_id = item.id_path.replace(o_id_path, '');
            const temp_name = item.name_path.replace(o_name_path, '');
            item.id_path = dept.id_path + temp_id;
            item.name_path = dept.name_path + temp_name;
            await item.save({ transaction });
          }
        })
      );
      await transaction.commit();
      return ctx.apiSuccesss(res);

    } catch (err) {
      await transaction.rollback();
      return ctx.apiFail('部门移动失败:' + err);
    }
  }

  // 测试通过事务控制循环更新
  async testUpdateCycle() {
    const { ctx, app } = this;
    const children = await app.model.Department.findAll({
      where: {
        pid: 8,
      },
    });
    let transaction;
    try {
      transaction = await this.ctx.model.transaction();
      // 方法1: 使用Promise.all，保证循环中所有内容都执行完毕后，才向后执行。
      // 这种方式，循环中的每一项都是并行执行的。
      await Promise.all(
        children.map(async item => {
          // if (item.id === 14) {
          //   throw new Error('故意抛错');
          // }
          item.description += '1';
          await item.save({ transaction });
        })
      );

      // 方法2: 使用for...of... 此种方式可以保证循环中是顺序执行，且是同步的
      // for (const item of children) {
      //   if (item.id === 14) {
      //     throw new Error('故意抛错');
      //   }
      //   await item.update({
      //     description: item.description + '1',
      //   }, { transaction });
      //   // console.log('--->', res);
      // }
      await transaction.commit();
      ctx.apiSuccesss(children);
    } catch (err) {
      await transaction.rollback();
      return ctx.apiFail('循环操作失败:' + err);
    }
  }

  // 测试对fn的使用，以及如何在where条件中使用function
  // 【特别注意】sequelize 的引用，const sequelize = app.model;
  async testFn() {
    const { ctx, app } = this;
    const sequelize = app.model;
    const where = app.model.where(sequelize.fn('char_length', sequelize.col('name')), 7);
    const depts = await app.model.Department.findAll({
      where,
    });
    ctx.apiSuccesss(depts);
  }

  // 测试原始查询
  // 【特别注意】sequelize 的引用，const sequelize = app.model;
  async testRawSelect() {
    const { ctx, app } = this;
    const sequelize = app.model;
    // query函数的第二个参数，是一个对象，对象里面几个常用参数进行说明。
    // 1. pain：如果plain为真，那么sequelize只返回第一个 记录结果集。如果为false，则返回所有记录。
    // 2. type：正在执行的查询的类型（也可以is hi更新哦，具体哪些去看官网api）。查询类型影响返回结果之前的格式化方式。
    // 3. raw：查询对类型是否有模型定义，如果您的查询没有模型定义，请将此设置为true。
    // 4. logging: console.log记录查询的函数 是否会为发送的每个SQL查询调用 到服务器。
    const depts = await sequelize.query('SELECT * FROM `sys_department` ', { type: sequelize.QueryTypes.SELECT, model: app.model.Department, mapToModel: true, plain: true });
    // 这样查出的对象就相当于是实体对象了，可以按照实体对象的方式来处理
    depts.description += '11111';
    depts.save();
    ctx.apiSuccesss(depts);
  }

  // 测试原始修改
  // 【特别注意】sequelize 的引用，const sequelize = app.model;
  async testRawUpdate() {
    const { ctx, app } = this;
    const sequelize = app.model;
    // 特别注意，mysql中，字符串都是要使用双引号的
    const [ results, metadata ] = await sequelize.query('update sys_department set description = CONCAT(description, "111") where id = 14 ');
    console.log('results=', results);
    console.log('metadata=', metadata);
    ctx.apiSuccesss(results);
  }

  async testGetAssociation() {
    const { ctx, app } = this;
    const dept = await ctx.model.Department.findOne({
      include: [{
        model: app.model.Department,
        as: 'parant',
      }, {
        model: app.model.Department,
        as: 'children',
      }, {
        model: app.model.Admin,
        as: 'members',
        attributes: [ 'id', 'no', 'department_id', 'username', 'name' ],
      }],
      where: {
        id: 2,
      },
    });
    ctx.apiSuccesss(dept);
  }
}

module.exports = DepartmentController;
